From: Colin Walters Date: Thu, 17 Feb 2011 22:07:13 +0000 (-0500) Subject: Add a configure option --enable-jhbuild-wrapper-script X-Git-Tag: archive/raspbian/50.5-1+rpi1~1^2~1013^2^2~8829 X-Git-Url: https://dgit.raspbian.org/%22http://www.example.com/cgi/%22/%22http:/www.example.com/cgi/%22?a=commitdiff_plain;h=7fc6a3670ccb0b03094e12e6ee19bf01b0030721;p=gnome-shell.git Add a configure option --enable-jhbuild-wrapper-script The current gnome-shell.in script has a huge amount of unnecessary complexity for the installed, normal case. Fix this by adding a configure option (defaulting to false) that installs a simple, obvious wrapper script around mutter. We do change the gnome-shell build setup to pass this option by default for jhbuild. https://bugzilla.gnome.org/show_bug.cgi?id=642084 --- diff --git a/.gitignore b/.gitignore index e01f1ea24..035bfce28 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,9 @@ src/Makefile src/Makefile.in src/gnomeshell-taskpanel src/gnome-shell +src/gnome-shell-extension-tool +src/gnome-shell-installed +src/gnome-shell-jhbuild src/run-js-test src/test-recorder src/test-recorder.ogg diff --git a/configure.ac b/configure.ac index 8155bae4a..1fb3bdd9a 100644 --- a/configure.ac +++ b/configure.ac @@ -187,6 +187,24 @@ if test "$enable_compile_warnings" != no ; then fi changequote([,])dnl +AC_ARG_ENABLE(jhbuild-wrapper-script, + AS_HELP_STRING([--jhbuild-wrapper-script=yes],[Make "gnome-shell" script work for jhbuild]),,enable_jhbuild_wrapper_script=no) +AM_CONDITIONAL(USE_JHBUILD_WRAPPER_SCRIPT, test "x$enable_jhbuild_wrapper_script" = xyes) + +AC_MSG_CHECKING([for Ubuntu]) +if test -f /etc/ubuntu_version; then + enable_dynamic_mozjs_hack_default=yes + AC_MSG_RESULT([yes]) +else + enable_dynamic_mozjs_hack_default=no + AC_MSG_RESULT([no]) +fi + +AC_ARG_ENABLE(dynamic-mozjs-hack, + AS_HELP_STRING([--dynamic-mozjs-hack=no],[Look for libmozjs.so using pkg-config]),,enable_dynamic_mozjs_hack=$enable_dynamic_mozjs_hack_default) +ENABLE_DYNAMIC_MOZJS_HACK=$enable_dynamic_mozjs_hack +AC_SUBST(ENABLE_DYNAMIC_MOZJS_HACK) + AC_PATH_PROG(mutter, [mutter]) AC_SUBST(mutter) diff --git a/src/Makefile.am b/src/Makefile.am index 170913b35..c5fd0aa04 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2,32 +2,44 @@ NULL = BUILT_SOURCES = CLEANFILES = EXTRA_DIST = +bin_SCRIPTS = libexec_PROGRAMS = noinst_LTLIBRARIES = noinst_PROGRAMS = .AUTOPARALLEL: -generated_bin_scripts = gnome-shell gnome-shell-extension-tool - -bin_SCRIPTS = $(generated_bin_scripts) +generated_bin_scripts = gnome-shell-installed gnome-shell-jhbuild gnome-shell-extension-tool +bin_SCRIPTS += gnome-shell $(generated_bin_scripts) generated_script_substitutions = -e "s|@MUTTER_BIN_DIR[@]|$(MUTTER_BIN_DIR)|" \ -e "s|@datadir[@]|$(datadir)|" \ -e "s|@libexecdir[@]|$(libexecdir)|" \ -e "s|@libdir[@]|$(libdir)|" \ -e "s|@pkgdatadir[@]|$(pkgdatadir)|" \ + -e "s|@ENABLE_DYNAMIC_MOZJS_HACK[@]|$(ENABLE_DYNAMIC_MOZJS_HACK)|" \ -e "s|@PYTHON[@]|$(PYTHON)|" \ -e "s|@VERSION[@]|$(VERSION)|" \ -e "s|@sysconfdir[@]|$(sysconfdir)|" -gnome-shell: gnome-shell.in +gnome-shell-jhbuild: gnome-shell-jhbuild.in Makefile + $(AM_V_GEN) sed $(generated_script_substitutions) $< > $@.tmp && mv $@.tmp $@ && chmod a+x $@ + +gnome-shell-installed: gnome-shell-installed.in Makefile $(AM_V_GEN) sed $(generated_script_substitutions) $< > $@.tmp && mv $@.tmp $@ && chmod a+x $@ -gnome-shell-extension-tool: gnome-shell-extension-tool.in +gnome-shell-extension-tool: gnome-shell-extension-tool.in Makefile $(AM_V_GEN) sed $(generated_script_substitutions) $< > $@.tmp && mv $@.tmp $@ && chmod a+x $@ -CLEANFILES += $(generated_bin_scripts) +if USE_JHBUILD_WRAPPER_SCRIPT +gnome-shell: gnome-shell-jhbuild Makefile + cp $< $@.tmp && mv $@.tmp $@ +else +gnome-shell: gnome-shell-installed Makefile + cp $< $@.tmp && mv $@.tmp $@ +endif + +CLEANFILES += gnome-shell $(generated_bin_scripts) EXTRA_DIST += $(generated_bin_scripts:=.in) include Makefile-gdmuser.am diff --git a/src/gnome-shell-installed.in b/src/gnome-shell-installed.in new file mode 100644 index 000000000..3e98e4112 --- /dev/null +++ b/src/gnome-shell-installed.in @@ -0,0 +1,41 @@ +#!/bin/bash +# gnome-shell is a plugin for mutter; start mutter with the right +# options. + +# About the value of NO_GAIL and NO_AT_BRIDGE: If a11y is enabled, +# gtk_init() will normally load gail and at-bridge. But we don't +# want at-bridge to be loaded until after clutter is initialized +# (which mutter does after initializing gtk) and we don't want +# gail to be loaded at all. So set these flags. shell_a11y_init() +# will clear them so they don't get passed to gnome-shell's +# children. +export NO_GAIL=1 +export NO_AT_BRIDGE=1 + +# FIXME - Add gjs API to set this stuff and don't depend on the +# environment. These propagate to child processes. +export GJS_DEBUG_OUTPUT='stderr' +# By default only let gjs show errors and things that are explicitly +# logged via log() from javascript +export GJS_DEBUG_TOPICS='JS ERROR;JS LOG' + +if test -z "$MUTTER_PLUGINS"; then + MUTTER_PLUGINS=libgnome-shell +fi + +# Work around Ubuntu xulrunner bug, +# http://bugzilla.gnome.org/show_bug.cgi?id=573413 +if test x@ENABLE_DYNAMIC_MOZJS_HACK@ = xyes; then + sdkdir=$(pkg-config --variable=sdkdir mozilla-js) + if test -n "${sdkdir}"; then + mozjs_libdir=$(echo "$sdkdir" | sed -e s,'-\(sdk\|devel\)',,) + if test -n "$LD_LIBRARY_PATH"; then + LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$mozjs_libdir + else + LD_LIBRARY_PATH=$mozjs_libdir + fi + export LD_LIBRARY_PATH + fi +fi + +exec mutter --mutter-plugins=$MUTTER_PLUGINS "$@" diff --git a/src/gnome-shell-jhbuild.in b/src/gnome-shell-jhbuild.in new file mode 100755 index 000000000..bca0e32f0 --- /dev/null +++ b/src/gnome-shell-jhbuild.in @@ -0,0 +1,540 @@ +#!@PYTHON@ +# -*- mode: Python; indent-tabs-mode: nil; -*- + +import atexit +import datetime +import dbus +from dbus.mainloop.glib import DBusGMainLoop +import gobject +try: + import json +except ImportError: + try: + import simplejson as json + except ImportError: + json = None +import optparse +import os +import random +import re +import shutil +import signal +import subprocess +import sys +import tempfile +import termios +import time +import errno + +def show_version(option, opt_str, value, parser): + print "GNOME Shell @VERSION@" + sys.exit() + +def get_running_session_environs(): + wanted_environment = ['DBUS_SESSION_BUS_ADDRESS', 'DISPLAY', 'XDG_DATA_DIRS', + 'XAUTHORITY', 'XDG_SESSION_COOKIE', 'ORBIT_SOCKETDIR', + 'SESSION_MANAGER'] + num_re = re.compile('^[0-9]+$') + myuid = os.getuid() + if not os.path.isdir('/proc'): + return {} + for filename in os.listdir('/proc'): + if not num_re.match(filename): + continue + piddir = '/proc/' + filename + try: + stat = os.stat(piddir) + except OSError, e: + continue + if not stat.st_uid == myuid: + continue + try: + f = open(piddir + "/cmdline") + command = f.read() + f.close() + except IOError, e: + continue + # /proc/cmdline is separated and terminated by NULs + command = command.split("\x00")[0] + command = os.path.basename(command) + if command != 'gnome-session': + continue + try: + f = open(os.path.join(piddir, 'environ')) + except OSError, e: + continue + environ_data = f.read() + f.close() + # There's a trailing null at the last one, so remove the + # empty string + environs = environ_data.split('\0')[:-1] + # Rumor has it the presence of just FOO (instead of FOO=bar) + # represents a deleted environment variable + environs = filter(lambda x: '=' in x, environs) + # Turn it into a dictionary + environs = dict(map(lambda x: x.split('=', 1), environs)) + result = {} + for key in wanted_environment: + if key in environs: + result[key] = environs[key] + return result + +def start_dconf_await_service(): + DCONF_NAME = 'ca.desrt.dconf' + + dbus_loop = DBusGMainLoop() + bus = dbus.SessionBus(mainloop=dbus_loop) + + # See if the service is already running or normal D-Bus activation works + need_manual_activate = False + try: + dconf_proxy = bus.get_object(DCONF_NAME, '/') + dconf_proxy.Ping(dbus_interface='org.freedesktop.DBus.Peer') + except dbus.exceptions.DBusException, e: + if e.get_dbus_name() == 'org.freedesktop.DBus.Error.ServiceUnknown': + need_manual_activate = True + else: + raise e + + if not need_manual_activate: + return + + # At this point, it looks like we just have a jhbuild install + # of dconf, not known to the session dbus-daemon, so we start + # it manually and wait for it to join the bus + + print "Starting dconf-service... ", + sys.stdout.flush() + + # dconf is linked without libtool, so unlike other GNOME modules, + # won't have an embedded rpath for its library directory. + env = dict(os.environ) + if 'LD_LIBRARY_PATH' in env and env['LD_LIBRARY_PATH']: + ld_library_path = '@libdir@:' + env['LD_LIBRARY_PATH'] + else: + ld_library_path = '@libdir@' + env['LD_LIBRARY_PATH'] = ld_library_path + + dconf_path = os.path.join('@libexecdir@', 'dconf-service') + try: + subprocess.Popen([dconf_path, '--keep-alive'], env=env) + except OSError, e: + print "\nFailed to start %s: %s" % (dconf_path, e) + sys.exit(1) + + bus_proxy = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus') + bus_iface = dbus.Interface(bus_proxy, 'org.freedesktop.DBus') + + loop = gobject.MainLoop() + + def on_name_owner_changed(name, prev_owner, new_owner): + if not (name == DCONF_NAME and new_owner != ''): + return + print "started" + loop.quit() + return + bus_iface.connect_to_signal('NameOwnerChanged', on_name_owner_changed) + + def on_timeout(): + print "\nFailed to start %s: timed out" % (dconf_path,) + sys.exit(1) + gobject.timeout_add_seconds(7, on_timeout) + + loop.run() + +def start_shell(perf_output=None): + bin_dir = os.path.dirname(os.path.abspath(sys.argv[0])) + if os.path.exists(os.path.join(bin_dir, 'gnome-shell.in')): + running_from_source_tree = True + top_dir = os.path.dirname(bin_dir) + plugin = os.path.join(top_dir, 'src', 'libgnome-shell.la') + typelib_dir = os.path.join(top_dir, "src") + js_dir = os.path.join(top_dir, "js") + data_dir = os.path.join(top_dir, "data") + else: + running_from_source_tree = False + plugin = 'libgnome-shell' + js_dir = os.path.join('@pkgdatadir@', 'js') + + # Set up environment + env = dict(os.environ) + # TODO: Fix this, since nothing prevents it from propagating to child + # processes. Why is it even here? + env.update({'GNOME_DISABLE_CRASH_DIALOG' : '1'}) + # This stuff should really should only happen when running + # uninstalled, i.e. it should be in the conditional + # below. Otherwise it's just a bad reimplementation of "jhbuild + # run". See bug #642084 + env.update({'GNOME_SHELL_JS' : js_dir, + 'PATH' : '@MUTTER_BIN_DIR@:' + os.environ.get('PATH', ''), + 'XDG_CONFIG_DIRS' : '@sysconfdir@/xdg:' + (os.environ.get('XDG_CONFIG_DIRS') or '/etc/xdg'), + 'XDG_DATA_DIRS' : '@datadir@:' + (os.environ.get('XDG_DATA_DIRS') or '/usr/local/share:/usr/share')}) + if running_from_source_tree: + if os.environ.has_key('GI_TYPELIB_PATH'): + typelib_dir = typelib_dir + ":" + os.environ.get('GI_TYPELIB_PATH') + env.update({'GNOME_SHELL_BINDIR' : bin_dir, + 'GNOME_SHELL_DATADIR' : data_dir, + 'GI_TYPELIB_PATH' : typelib_dir, + 'GSETTINGS_SCHEMA_DIR' : data_dir }) + else: + # This is just broken to set in the installed case; see bug #642084 + env.update({'GSETTINGS_SCHEMA_DIR' : os.path.join('@datadir@', 'glib-2.0', 'schemas')}) + + # Also plain broken to set in the normal installed case + jhbuild_gconf_source = os.path.join('@sysconfdir@', 'gconf/2/path.jhbuild') + if os.path.exists(jhbuild_gconf_source): + env['GCONF_DEFAULT_SOURCE_PATH'] = jhbuild_gconf_source + + if options.perf is not None: + env['SHELL_PERF_MODULE'] = options.perf + + if perf_output is not None: + env['SHELL_PERF_OUTPUT'] = perf_output + + if options.debug: + debug_command = options.debug_command.split() + args = list(debug_command) + else: + args = [] + + if running_from_source_tree: + env['MUTTER_PLUGINS'] = plugin + args.append(os.path.join(bin_dir, 'gnome-shell-installed')) + if options.replace: + args.append('--replace') + if options.sync: + args.append('--sync') + return subprocess.Popen(args, env=env) + +def run_shell(perf_output=None): + if options.debug: + # Record initial terminal state so we can reset it to that + # later, in case we kill gdb at a bad time + termattrs = termios.tcgetattr(0); + + normal_exit = False + + if options.verbose: + print "Starting shell" + + try: + shell = start_shell(perf_output=perf_output) + + # Wait for shell to exit + if options.verbose: + print "Waiting for shell to exit" + shell.wait() + + except KeyboardInterrupt, e: + try: + os.kill(shell.pid, signal.SIGKILL) + except: + pass + shell.wait() + finally: + if shell is None: + print "Failed to start shell" + elif shell.returncode == 0: + normal_exit = True + if options.verbose: + print "Shell exited normally" + elif shell.returncode < 0: + # Python has no mapping for strsignal; not worth using + # ctypes for this. + print "Shell killed with signal %d" % - shell.returncode + else: + # Normal reason here would be losing connection the X server + if options.verbose: + print "Shell exited with return code %d" % shell.returncode + + if options.debug: + termios.tcsetattr(0, termios.TCSANOW, termattrs); + + return normal_exit + +def upload_performance_report(report_text): + # Local imports to avoid impacting gnome-shell startup time + import base64 + from ConfigParser import RawConfigParser + import hashlib + import hmac + import httplib + import urlparse + import urllib + + try: + config_home = os.environ['XDG_CONFIG_HOME'] + except KeyError: + config_home = None + + if not config_home: + config_home = os.path.expanduser("~/.config") + + config_file = os.path.join(config_home, "gnome-shell/perf.ini") + + try: + config = RawConfigParser() + f = open(config_file) + config.readfp(f) + f.close() + + base_url = config.get('upload', 'url') + system_name = config.get('upload', 'name') + secret_key = config.get('upload', 'key') + except Exception, e: + print "Can't read upload configuration from %s: %s" % (config_file, str(e)) + sys.exit(1) + + # Determine host, port and upload URL from provided data, we're + # a bit extra-careful about normalization since the URL is part + # of the signature. + + split = urlparse.urlsplit(base_url) + scheme = split[0].lower() + netloc = split[1] + base_path = split[2] + + m = re.match(r'^(.*?)(?::(\d+))?$', netloc) + if m.group(2): + host, port = m.group(1), int(m.group(2)) + else: + host, port = m.group(1), None + + if scheme != "http": + print "'%s' is not a HTTP URL" % base_url + sys.exit(1) + + if port is None: + port = 80 + + if base_path.endswith('/'): + base_path = base_path[:-1] + + if port == 80: + normalized_base = "%s://%s%s" % (scheme, host, base_path) + else: + normalized_base = "%s://%s:%d%s" % (scheme, host, port, base_path) + + upload_url = normalized_base + '/system/%s/upload' % system_name + upload_path = urlparse.urlsplit(upload_url)[2] # path portion + + # Create signature based on upload URL and the report data + + signature_data = 'POST&' + upload_url + "&&" + h = hmac.new(secret_key, digestmod=hashlib.sha1) + h.update(signature_data) + h.update(report_text) + signature = urllib.quote(base64.b64encode(h.digest()), "~") + + headers = { + 'User-Agent': 'gnome-shell', + 'Content-Type': 'application/json', + 'X-Shell-Signature': 'HMAC-SHA1 ' + signature + }; + + connection = httplib.HTTPConnection(host, port) + connection.request('POST', upload_path, report_text, headers) + response = connection.getresponse() + + if response.status == 200: + print "Performance report upload succeeded" + else: + print "Performance report upload failed with status %d" % response.status + print response.read() + +def run_performance_test(): + iters = options.perf_iters + if options.perf_warmup: + iters += 1 + + logs = [] + metric_summaries = {} + + for i in xrange(0, iters): + # We create an empty temporary file that the shell will overwrite + # with the contents. + handle, output_file = tempfile.mkstemp(".json", "gnome-shell-perf.") + os.close(handle) + + # Run the performance test and collect the output as JSON + normal_exit = False + try: + normal_exit = run_shell(perf_output=output_file) + finally: + if not normal_exit: + os.remove(output_file) + + if not normal_exit: + return False + + try: + f = open(output_file) + output = json.load(f) + f.close() + finally: + os.remove(output_file) + + # Grab the event definitions and monitor layout the first time around + if i == 0: + events = output['events'] + monitors = output['monitors'] + + if options.perf_warmup and i == 0: + continue + + for metric in output['metrics']: + name = metric['name'] + if not name in metric_summaries: + summary = {} + summary['description'] = metric['description'] + summary['units'] = metric['units'] + summary['values'] = [] + metric_summaries[name] = summary + else: + summary = metric_summaries[name] + + summary['values'].append(metric['value']) + + logs.append(output['log']) + + if options.perf_output or options.perf_upload: + # Write a complete report, formatted as JSON. The Javascript/C code that + # generates the individual reports we are summarizing here is very careful + # to format them nicely, but we just dump out a compressed no-whitespace + # version here for simplicity. Using json.dump(indent=0) doesn't real + # improve the readability of the output much. + report = { + 'date': datetime.datetime.utcnow().isoformat() + 'Z', + 'events': events, + 'monitors': monitors, + 'metrics': metric_summaries, + 'logs': logs + } + + # Add the Git revision if available + bin_dir = os.path.dirname(os.path.abspath(sys.argv[0])) + if os.path.exists(os.path.join(bin_dir, 'gnome-shell.in')): + top_dir = os.path.dirname(bin_dir) + git_dir = os.path.join(top_dir, '.git') + if os.path.exists(git_dir): + env = dict(os.environ) + env['GIT_DIR'] = git_dir + revision = subprocess.Popen(['git', 'rev-parse', 'HEAD'], + env=env, + stdout=subprocess.PIPE).communicate()[0].strip() + report['revision'] = revision + + if options.perf_output: + f = open(options.perf_output, 'w') + json.dump(report, f) + f.close() + + if options.perf_upload: + upload_performance_report(json.dumps(report)) + else: + # Write a human readable summary + print '------------------------------------------------------------'; + for metric in sorted(metric_summaries.keys()): + summary = metric_summaries[metric] + print "#", summary['description'] + print metric, ", ".join((str(x) for x in summary['values'])) + print '------------------------------------------------------------'; + + return True + +def restore_gnome(): + # Do imports lazily to save time and memory + import gio + import gconf + + # We don't want to start the new gnome-panel in the current + # directory; $HOME is better for stuff launched from it + os.chdir(os.path.expanduser("~")) + + def launch_component(gconf_path): + client = gconf.client_get_default() + component = client.get_string(gconf_path) + + if component == None or component == "": + return + + # See gnome-session/gsm-util.c:gsm_util_find_desktop_file_for_app_name() + # The one difference is that we don't search the autostart directories, + # and just search normal application search path. (Gio doesnt' know + # how to search the autostart dirs, so we'd have to do that ourselves.) + appinfo = None + try: + appinfo = gio.unix.DesktopAppInfo(component + ".desktop") + except: + try: + appinfo = gio.unix.DesktopAppInfo("gnome-" + component + ".desktop") + except: + pass + + if appinfo: + appinfo.launch() + + launch_component("/desktop/gnome/session/required_components/windowmanager") + launch_component("/desktop/gnome/session/required_components/panel") + +# Main program + +parser = optparse.OptionParser() +parser.add_option("-r", "--replace", action="store_true", + help="Replace the running metacity/gnome-panel") +parser.add_option("-g", "--debug", action="store_true", + help="Run under a debugger") +parser.add_option("", "--debug-command", metavar="COMMAND", + help="Command to use for debugging (defaults to 'gdb --args')") +parser.add_option("-v", "--verbose", action="store_true") +parser.add_option("", "--sync", action="store_true") +parser.add_option("", "--perf", metavar="PERF_MODULE", + help="Specify the name of a performance module to run") +parser.add_option("", "--perf-iters", type="int", metavar="ITERS", + help="Numbers of iterations of performance module to run", + default=1) +parser.add_option("", "--perf-warmup", action="store_true", + help="Run a dry run before performance tests") +parser.add_option("", "--perf-output", metavar="OUTPUT_FILE", + help="Output file to write performance report") +parser.add_option("", "--perf-upload", action="store_true", + help="Upload performance report to server") +parser.add_option("", "--version", action="callback", callback=show_version, + help="Display version and exit") + +options, args = parser.parse_args() + +if args: + parser.print_usage() + sys.exit(1) + +if options.perf and json is None: + print 'The Python simplejson module is required for performance tests' + sys.exit(1) + +# Handle ssh logins +if 'DISPLAY' not in os.environ: + running_env = get_running_session_environs() + os.environ.update(running_env) + +if options.debug_command: + options.debug = True +elif options.debug: + options.debug_command = "gdb --args" + +# We only respawn the previous environment on abnormal exit; +# for a clean exit, we assume that gnome-shell was replaced with +# something else. +normal_exit = False + +# Make sure dconf daemon is running +start_dconf_await_service() + +try: + if options.perf: + normal_exit = run_performance_test() + else: + normal_exit = run_shell() +finally: + if options.replace and (options.perf or not normal_exit): + restore_gnome() diff --git a/src/gnome-shell.in b/src/gnome-shell.in deleted file mode 100755 index 984abc66e..000000000 --- a/src/gnome-shell.in +++ /dev/null @@ -1,565 +0,0 @@ -#!@PYTHON@ -# -*- mode: Python; indent-tabs-mode: nil; -*- - -import atexit -import datetime -import dbus -from dbus.mainloop.glib import DBusGMainLoop -import gobject -try: - import json -except ImportError: - try: - import simplejson as json - except ImportError: - json = None -import optparse -import os -import random -import re -import shutil -import signal -import subprocess -import sys -import tempfile -import termios -import time -import errno - -def show_version(option, opt_str, value, parser): - print "GNOME Shell @VERSION@" - sys.exit() - -def get_running_session_environs(): - wanted_environment = ['DBUS_SESSION_BUS_ADDRESS', 'DISPLAY', 'XDG_DATA_DIRS', - 'XAUTHORITY', 'XDG_SESSION_COOKIE', 'ORBIT_SOCKETDIR', - 'SESSION_MANAGER'] - num_re = re.compile('^[0-9]+$') - myuid = os.getuid() - if not os.path.isdir('/proc'): - return {} - for filename in os.listdir('/proc'): - if not num_re.match(filename): - continue - piddir = '/proc/' + filename - try: - stat = os.stat(piddir) - except OSError, e: - continue - if not stat.st_uid == myuid: - continue - try: - f = open(piddir + "/cmdline") - command = f.read() - f.close() - except IOError, e: - continue - # /proc/cmdline is separated and terminated by NULs - command = command.split("\x00")[0] - command = os.path.basename(command) - if command != 'gnome-session': - continue - try: - f = open(os.path.join(piddir, 'environ')) - except OSError, e: - continue - environ_data = f.read() - f.close() - # There's a trailing null at the last one, so remove the - # empty string - environs = environ_data.split('\0')[:-1] - # Rumor has it the presence of just FOO (instead of FOO=bar) - # represents a deleted environment variable - environs = filter(lambda x: '=' in x, environs) - # Turn it into a dictionary - environs = dict(map(lambda x: x.split('=', 1), environs)) - result = {} - for key in wanted_environment: - if key in environs: - result[key] = environs[key] - return result - -def start_dconf_await_service(): - DCONF_NAME = 'ca.desrt.dconf' - - dbus_loop = DBusGMainLoop() - bus = dbus.SessionBus(mainloop=dbus_loop) - - # See if the service is already running or normal D-Bus activation works - need_manual_activate = False - try: - dconf_proxy = bus.get_object(DCONF_NAME, '/') - dconf_proxy.Ping(dbus_interface='org.freedesktop.DBus.Peer') - except dbus.exceptions.DBusException, e: - if e.get_dbus_name() == 'org.freedesktop.DBus.Error.ServiceUnknown': - need_manual_activate = True - else: - raise e - - if not need_manual_activate: - return - - # At this point, it looks like we just have a jhbuild install - # of dconf, not known to the session dbus-daemon, so we start - # it manually and wait for it to join the bus - - print "Starting dconf-service... ", - sys.stdout.flush() - - # dconf is linked without libtool, so unlike other GNOME modules, - # won't have an embedded rpath for its library directory. - env = dict(os.environ) - if 'LD_LIBRARY_PATH' in env and env['LD_LIBRARY_PATH']: - ld_library_path = '@libdir@:' + env['LD_LIBRARY_PATH'] - else: - ld_library_path = '@libdir@' - env['LD_LIBRARY_PATH'] = ld_library_path - - dconf_path = os.path.join('@libexecdir@', 'dconf-service') - try: - subprocess.Popen([dconf_path, '--keep-alive'], env=env) - except OSError, e: - print "\nFailed to start %s: %s" % (dconf_path, e) - sys.exit(1) - - bus_proxy = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus') - bus_iface = dbus.Interface(bus_proxy, 'org.freedesktop.DBus') - - loop = gobject.MainLoop() - - def on_name_owner_changed(name, prev_owner, new_owner): - if not (name == DCONF_NAME and new_owner != ''): - return - print "started" - loop.quit() - return - bus_iface.connect_to_signal('NameOwnerChanged', on_name_owner_changed) - - def on_timeout(): - print "\nFailed to start %s: timed out" % (dconf_path,) - sys.exit(1) - gobject.timeout_add_seconds(7, on_timeout) - - loop.run() - -def start_shell(perf_output=None): - bin_dir = os.path.dirname(os.path.abspath(sys.argv[0])) - if os.path.exists(os.path.join(bin_dir, 'gnome-shell.in')): - running_from_source_tree = True - top_dir = os.path.dirname(bin_dir) - plugin = os.path.join(top_dir, 'src', 'libgnome-shell.la') - typelib_dir = os.path.join(top_dir, "src") - js_dir = os.path.join(top_dir, "js") - data_dir = os.path.join(top_dir, "data") - else: - running_from_source_tree = False - plugin = 'libgnome-shell' - js_dir = os.path.join('@pkgdatadir@', 'js') - - # Set up environment - - # About the value of NO_GAIL and NO_AT_BRIDGE: If a11y is enabled, - # gtk_init() will normally load gail and at-bridge. But we don't - # want at-bridge to be loaded until after clutter is initialized - # (which mutter does after initializing gtk) and we don't want - # gail to be loaded at all. So set these flags. shell_a11y_init() - # will clear them so they don't get passed to gnome-shell's - # children. - - env = dict(os.environ) - env.update({'GNOME_SHELL_JS' : js_dir, - 'PATH' : '@MUTTER_BIN_DIR@:' + os.environ.get('PATH', ''), - 'XDG_CONFIG_DIRS' : '@sysconfdir@/xdg:' + (os.environ.get('XDG_CONFIG_DIRS') or '/etc/xdg'), - 'XDG_DATA_DIRS' : '@datadir@:' + (os.environ.get('XDG_DATA_DIRS') or '/usr/local/share:/usr/share'), - 'GNOME_DISABLE_CRASH_DIALOG' : '1', - 'NO_GAIL' : '1', - 'NO_AT_BRIDGE' : '1'}) - - if running_from_source_tree: - if os.environ.has_key('GI_TYPELIB_PATH'): - typelib_dir = typelib_dir + ":" + os.environ.get('GI_TYPELIB_PATH') - env.update({'GNOME_SHELL_BINDIR' : bin_dir, - 'GNOME_SHELL_DATADIR' : data_dir, - 'GI_TYPELIB_PATH' : typelib_dir, - 'GSETTINGS_SCHEMA_DIR' : data_dir }) - else: - env.update({'GSETTINGS_SCHEMA_DIR' : os.path.join('@datadir@', 'glib-2.0', 'schemas')}) - - jhbuild_gconf_source = os.path.join('@sysconfdir@', 'gconf/2/path.jhbuild') - if os.path.exists(jhbuild_gconf_source): - env['GCONF_DEFAULT_SOURCE_PATH'] = jhbuild_gconf_source - - # Work around Ubuntu xulrunner bug, - # http://bugzilla.gnome.org/show_bug.cgi?id=573413 - pkgconfig = subprocess.Popen(['pkg-config', '--variable=sdkdir', 'mozilla-js'], - stdout=subprocess.PIPE) - mozjs_sdkdir = pkgconfig.communicate()[0].strip() - pkgconfig.wait() - if pkgconfig.returncode == 0: - mozjs_libdir = re.sub('-(sdk|devel)', '', mozjs_sdkdir) - if os.path.exists(mozjs_libdir + '/libmozjs.so'): - if 'LD_LIBRARY_PATH' in env and env['LD_LIBRARY_PATH']: - ld_library_path = env['LD_LIBRARY_PATH'] + ':' + mozjs_libdir - else: - ld_library_path = mozjs_libdir - env['LD_LIBRARY_PATH'] = ld_library_path - - # Log everything to stderr (make stderr our "log file") - env['GJS_DEBUG_OUTPUT'] = 'stderr' - - if not options.verbose: - # Unless verbose() is specified, only let gjs show errors and - # things that are explicitly logged via log() from javascript - env['GJS_DEBUG_TOPICS'] = 'JS ERROR;JS LOG' - - if options.perf is not None: - env['SHELL_PERF_MODULE'] = options.perf - - if perf_output is not None: - env['SHELL_PERF_OUTPUT'] = perf_output - - if options.debug: - debug_command = options.debug_command.split() - args = list(debug_command) - else: - args = [] - - args.extend(['mutter', '--mutter-plugins=' + plugin]) - if options.replace: - args.append('--replace') - if options.sync: - args.append('--sync') - return subprocess.Popen(args, env=env) - -def run_shell(perf_output=None): - if options.debug: - # Record initial terminal state so we can reset it to that - # later, in case we kill gdb at a bad time - termattrs = termios.tcgetattr(0); - - normal_exit = False - - if options.verbose: - print "Starting shell" - - try: - shell = start_shell(perf_output=perf_output) - - # Wait for shell to exit - if options.verbose: - print "Waiting for shell to exit" - shell.wait() - - except KeyboardInterrupt, e: - try: - os.kill(shell.pid, signal.SIGKILL) - except: - pass - shell.wait() - finally: - if shell is None: - print "Failed to start shell" - elif shell.returncode == 0: - normal_exit = True - if options.verbose: - print "Shell exited normally" - elif shell.returncode < 0: - # Python has no mapping for strsignal; not worth using - # ctypes for this. - print "Shell killed with signal %d" % - shell.returncode - else: - # Normal reason here would be losing connection the X server - if options.verbose: - print "Shell exited with return code %d" % shell.returncode - - if options.debug: - termios.tcsetattr(0, termios.TCSANOW, termattrs); - - return normal_exit - -def upload_performance_report(report_text): - # Local imports to avoid impacting gnome-shell startup time - import base64 - from ConfigParser import RawConfigParser - import hashlib - import hmac - import httplib - import urlparse - import urllib - - try: - config_home = os.environ['XDG_CONFIG_HOME'] - except KeyError: - config_home = None - - if not config_home: - config_home = os.path.expanduser("~/.config") - - config_file = os.path.join(config_home, "gnome-shell/perf.ini") - - try: - config = RawConfigParser() - f = open(config_file) - config.readfp(f) - f.close() - - base_url = config.get('upload', 'url') - system_name = config.get('upload', 'name') - secret_key = config.get('upload', 'key') - except Exception, e: - print "Can't read upload configuration from %s: %s" % (config_file, str(e)) - sys.exit(1) - - # Determine host, port and upload URL from provided data, we're - # a bit extra-careful about normalization since the URL is part - # of the signature. - - split = urlparse.urlsplit(base_url) - scheme = split[0].lower() - netloc = split[1] - base_path = split[2] - - m = re.match(r'^(.*?)(?::(\d+))?$', netloc) - if m.group(2): - host, port = m.group(1), int(m.group(2)) - else: - host, port = m.group(1), None - - if scheme != "http": - print "'%s' is not a HTTP URL" % base_url - sys.exit(1) - - if port is None: - port = 80 - - if base_path.endswith('/'): - base_path = base_path[:-1] - - if port == 80: - normalized_base = "%s://%s%s" % (scheme, host, base_path) - else: - normalized_base = "%s://%s:%d%s" % (scheme, host, port, base_path) - - upload_url = normalized_base + '/system/%s/upload' % system_name - upload_path = urlparse.urlsplit(upload_url)[2] # path portion - - # Create signature based on upload URL and the report data - - signature_data = 'POST&' + upload_url + "&&" - h = hmac.new(secret_key, digestmod=hashlib.sha1) - h.update(signature_data) - h.update(report_text) - signature = urllib.quote(base64.b64encode(h.digest()), "~") - - headers = { - 'User-Agent': 'gnome-shell', - 'Content-Type': 'application/json', - 'X-Shell-Signature': 'HMAC-SHA1 ' + signature - }; - - connection = httplib.HTTPConnection(host, port) - connection.request('POST', upload_path, report_text, headers) - response = connection.getresponse() - - if response.status == 200: - print "Performance report upload succeeded" - else: - print "Performance report upload failed with status %d" % response.status - print response.read() - -def run_performance_test(): - iters = options.perf_iters - if options.perf_warmup: - iters += 1 - - logs = [] - metric_summaries = {} - - for i in xrange(0, iters): - # We create an empty temporary file that the shell will overwrite - # with the contents. - handle, output_file = tempfile.mkstemp(".json", "gnome-shell-perf.") - os.close(handle) - - # Run the performance test and collect the output as JSON - normal_exit = False - try: - normal_exit = run_shell(perf_output=output_file) - finally: - if not normal_exit: - os.remove(output_file) - - if not normal_exit: - return False - - try: - f = open(output_file) - output = json.load(f) - f.close() - finally: - os.remove(output_file) - - # Grab the event definitions and monitor layout the first time around - if i == 0: - events = output['events'] - monitors = output['monitors'] - - if options.perf_warmup and i == 0: - continue - - for metric in output['metrics']: - name = metric['name'] - if not name in metric_summaries: - summary = {} - summary['description'] = metric['description'] - summary['units'] = metric['units'] - summary['values'] = [] - metric_summaries[name] = summary - else: - summary = metric_summaries[name] - - summary['values'].append(metric['value']) - - logs.append(output['log']) - - if options.perf_output or options.perf_upload: - # Write a complete report, formatted as JSON. The Javascript/C code that - # generates the individual reports we are summarizing here is very careful - # to format them nicely, but we just dump out a compressed no-whitespace - # version here for simplicity. Using json.dump(indent=0) doesn't real - # improve the readability of the output much. - report = { - 'date': datetime.datetime.utcnow().isoformat() + 'Z', - 'events': events, - 'monitors': monitors, - 'metrics': metric_summaries, - 'logs': logs - } - - # Add the Git revision if available - bin_dir = os.path.dirname(os.path.abspath(sys.argv[0])) - if os.path.exists(os.path.join(bin_dir, 'gnome-shell.in')): - top_dir = os.path.dirname(bin_dir) - git_dir = os.path.join(top_dir, '.git') - if os.path.exists(git_dir): - env = dict(os.environ) - env['GIT_DIR'] = git_dir - revision = subprocess.Popen(['git', 'rev-parse', 'HEAD'], - env=env, - stdout=subprocess.PIPE).communicate()[0].strip() - report['revision'] = revision - - if options.perf_output: - f = open(options.perf_output, 'w') - json.dump(report, f) - f.close() - - if options.perf_upload: - upload_performance_report(json.dumps(report)) - else: - # Write a human readable summary - print '------------------------------------------------------------'; - for metric in sorted(metric_summaries.keys()): - summary = metric_summaries[metric] - print "#", summary['description'] - print metric, ", ".join((str(x) for x in summary['values'])) - print '------------------------------------------------------------'; - - return True - -def restore_gnome(): - # Do imports lazily to save time and memory - import gio - import gconf - - # We don't want to start the new gnome-panel in the current - # directory; $HOME is better for stuff launched from it - os.chdir(os.path.expanduser("~")) - - def launch_component(gconf_path): - client = gconf.client_get_default() - component = client.get_string(gconf_path) - - if component == None or component == "": - return - - # See gnome-session/gsm-util.c:gsm_util_find_desktop_file_for_app_name() - # The one difference is that we don't search the autostart directories, - # and just search normal application search path. (Gio doesnt' know - # how to search the autostart dirs, so we'd have to do that ourselves.) - appinfo = None - try: - appinfo = gio.unix.DesktopAppInfo(component + ".desktop") - except: - try: - appinfo = gio.unix.DesktopAppInfo("gnome-" + component + ".desktop") - except: - pass - - if appinfo: - appinfo.launch() - - launch_component("/desktop/gnome/session/required_components/windowmanager") - launch_component("/desktop/gnome/session/required_components/panel") - -# Main program - -parser = optparse.OptionParser() -parser.add_option("-r", "--replace", action="store_true", - help="Replace the running metacity/gnome-panel") -parser.add_option("-g", "--debug", action="store_true", - help="Run under a debugger") -parser.add_option("", "--debug-command", metavar="COMMAND", - help="Command to use for debugging (defaults to 'gdb --args')") -parser.add_option("-v", "--verbose", action="store_true") -parser.add_option("", "--sync", action="store_true") -parser.add_option("", "--perf", metavar="PERF_MODULE", - help="Specify the name of a performance module to run") -parser.add_option("", "--perf-iters", type="int", metavar="ITERS", - help="Numbers of iterations of performance module to run", - default=1) -parser.add_option("", "--perf-warmup", action="store_true", - help="Run a dry run before performance tests") -parser.add_option("", "--perf-output", metavar="OUTPUT_FILE", - help="Output file to write performance report") -parser.add_option("", "--perf-upload", action="store_true", - help="Upload performance report to server") -parser.add_option("", "--version", action="callback", callback=show_version, - help="Display version and exit") - -options, args = parser.parse_args() - -if args: - parser.print_usage() - sys.exit(1) - -if options.perf and json is None: - print 'The Python simplejson module is required for performance tests' - sys.exit(1) - -# Handle ssh logins -if 'DISPLAY' not in os.environ: - running_env = get_running_session_environs() - os.environ.update(running_env) - -if options.debug_command: - options.debug = True -elif options.debug: - options.debug_command = "gdb --args" - -# We only respawn the previous environment on abnormal exit; -# for a clean exit, we assume that gnome-shell was replaced with -# something else. -normal_exit = False - -# Make sure dconf daemon is running -start_dconf_await_service() - -try: - if options.perf: - normal_exit = run_performance_test() - else: - normal_exit = run_shell() -finally: - if options.replace and (options.perf or not normal_exit): - restore_gnome() diff --git a/tools/build/gnome-shell.modules b/tools/build/gnome-shell.modules index 778db203f..b32104ef5 100644 --- a/tools/build/gnome-shell.modules +++ b/tools/build/gnome-shell.modules @@ -220,7 +220,7 @@ - +